/*
 * Adam Blackwell ada2358@ccs.neu.edu
 */
import java.util.Random;

public class MapTester {
  /**
   * Main method After compiling it can be run with: java MapTester Or
   * imported into Eclipse
   */
  public static void main(String[] args) {
    int totalInstances;
    int totalErrors = 0;
    totalInstances = 100;
    totalErrors += testRandomEmpty(totalInstances);
    totalErrors = testRandomArtifact(totalInstances);
    totalErrors += testRandomMerge(totalInstances);
    totalErrors += testExceptions();
    /* A few Stats... */
    System.out.println("\nNumber of errors found: " + totalErrors);
  }
  
  // Returns a random integer between 0 and max, inclusive
  private static int randomInt(int max) {
    return new Random().nextInt(max + 1);
  }
  
  // compare two maps for equality
  // without relying on the map's equals method
  private static boolean equalMaps(Map r1, Map r2) {
    if (!(r1.size().equals(r2.size())))
      return false;
    if (r1.isEmpty())
      return r2.isEmpty();
    if (r2.isEmpty())
      return r1.isEmpty();
    return (r1.firstArtifact().equals(r2.firstArtifact()) && equalMaps(
                                                                       r1
                                                                         .restArtifacts(),
                                                                       r2
                                                                         .restArtifacts()));
  }
  
  // string representation of a map
  // depends somewhat on the Map implementation being correct
  private static String stringOf(Map m) {
    Map tmp = m;
    String acc = "";
    while (!(tmp.isEmpty())) {
      acc = acc + tmp.firstArtifact().toString() + " ";
      tmp = tmp.restArtifacts();
    }
    return acc;
  }
  
  private static int length(Map m) {
    Map tmp = m;
    int len = 0;
    while (!(tmp.isEmpty())) {
      len++;
      tmp = tmp.restArtifacts();
    }
    return len;
  }
  
  // pick an artifact at random from Map m (m is length > 0)
  private static Artifact randomArtifactFromMap(Map m) {
    int len = length(m);
    if (len <= 0)
      throw new Error("Tester error: picking name from empty map");
    int r = randomInt(len - 1);
    Map tmp = m;
    while (r > 0) {
      r--;
      tmp = tmp.restArtifacts();
    }
    return tmp.firstArtifact();
  }
  
  // Test 'num' random instances of an empty map
  // Returns the number of errors
  private static int testRandomEmpty(int num) {
    int count = num;
    int errors = 0;
    while (count > 0) {
      errors = errors + testSingleRandomEmpty();
      count = count - 1;
    }
    return errors;
  }
  
  private static int testSingleRandomEmpty() {
    int w = randomInt(1000) + 1; // to avoid 0
    int h = randomInt(1000) + 1; // to avoid 0
    Map m = Map.empty(h, w);
    return testEmpty(m, h, w);
  }
  
  private static int testEmpty(Map m, int h, int w) {
    int errors = 0;
    String front = "Map.empty(" + h + "," + w + ").";
    try {
      if (!(assertT(m.size().equals(Coord.create(h, w)), front + "size()")))
        errors++;
      if (!(assertT(m.isEmpty() == true, front + "isEmpty()")))
        errors++;
      if (!(assertT(m.toString().equals(""), front + "toString()")))
        errors++;
      if (!(assertT(m.hashCode() == 0, front + "hashCode()")))
        errors++;
      // again, need an equals tester here
      // I'll skip for now because that's the answer to Question 2 :)
      return errors;
    } catch (Exception ex) {
      /*
       * If there was an exception anywhere in there, then we have a
       * problem
       */
      assertT(false, "Exception: " + ex.getMessage());
      return errors + 1;
    }
  }
  
  // Test 'num' random instances of a single artifact map
  // Returns the number of errors
  private static int testRandomArtifact(int num) {
    int count = num;
    int errors = 0;
    while (count > 0) {
      errors = errors + testSingleRandomArtifact();
      count = count - 1;
    }
    return errors;
  }
  
  private static int testSingleRandomArtifact() {
    int h = randomInt(1000) + 1; // to avoid 0
    int w = randomInt(1000) + 1; // to avoid 0
    int x = randomInt(h - 1);
    int y = randomInt(w - 1);
    String name = "art" + randomInt(1000000);
    Artifact art = Artifact.create(Coord.create(x, y), name);
    Map m = Map.artifact(h, w, art);
    return testArtifact(m, h, w, art);
  }
  
  private static int testArtifact(Map m, int h, int w, Artifact art) {
    int errors = 0;
    String front = "Map.artifact(" + h + "," + w + "," + art.toString() + ").";
    try {
      if (!(assertT(m.size().equals(Coord.create(h, w)), front + "size()")))
        errors++;
      if (!(assertT(m.isEmpty() == false, front + "isEmpty()")))
        errors++;
      if (!(assertT(m.firstArtifact().equals(art), front + "firstArtifact()")))
        errors++;
      if (!(assertT(equalMaps(m.restArtifacts(), Map.empty(h, w)),
                    front + "restArtifacts()")))
        errors++;
      int dx, dy;
      do {
        dx = randomInt(1000) - 500;
        dy = randomInt(1000) - 500;
      } while (art.position().xPos() + dx < 0 || art.position().yPos() + dy < 0
               || art.position().xPos() + dx >= h
               || art.position().yPos() + dy >= w);
      if (!(assertT(equalMaps(m.moveArtifact(art.name(), dx, dy),
                              Map.artifact(h, w, art.move(dx, dy))),
                    front + "moveArtifact(" + art.name() + "," + dx + "," + dy
                        + ")")))
        errors++;
      if (!(assertT(m.toString().equals(art.toString()), front + "toString()")))
        errors++;
      if (!(assertT(m.hashCode() == art.hashCode(), front + "hashCode()")))
        errors++;
      // again, need an equals tester here
      // I'll skip for now because that's the answer to Question 2
      return errors;
    } catch (Exception ex) {
      /*
       * If there was an exception anywhere in there, then we have a
       * problem
       */
      assertT(false, "Exception: " + ex.getMessage());
      return errors + 1;
    }
  }
  
  // Test 'num' random instances of a merged map
  // Returns the number of errors
  private static int testRandomMerge(int num) {
    int errors = 0;
    int  count = 0;
    while (count < num) {
      errors = errors + testSingleRandomMerge();
      count++;
    }
    return errors;
  }
  
  private static int testSingleRandomMerge() {
    // 
    Coord origin = Coord.create(0, 0);
    int[] randArray = new int[20];
    for (int i = 0; i < 20; i++) {
      randArray[i] = (int) Math.random() * 100;
    }
    Map m1 = Map.artifact(randArray[18] + 1000, randArray[19] + 1000, art0);
    Map m2 = Map.artifact(randArray[18] + 1000, randArray[19] + 1000, art1);
    Map m = Map.merge(m1, m2);
    return testMerge(m, m1, m2);
  }
  
  private static int testMerge(Map m, Map m1, Map m2) {
    int errors = 0;
    String front = "Map.merge(" + stringOf(m1) + "," + stringOf(m2) + ").";
    try {
      if (!(assertT(m.size().equals(m1.size()), front + "size()")))
        errors++;
      if (!(assertT(m.isEmpty() == (m1.isEmpty() && m2.isEmpty()),
                    front + "isEmpty()")))
        errors++;
      if (m1.isEmpty()) {
        if (!(assertT(m.firstArtifact().equals(m2.firstArtifact()),
                      front + "firstArtifact()")))
          errors++;
      } else {
        if (!(assertT(m.firstArtifact().equals(m1.firstArtifact()),
                      front + "firstArtifact()")))
          errors++;
      }
      if (m1.isEmpty()) {
        if (!(assertT(equalMaps(m.restArtifacts(), m2.restArtifacts()),
                      front + "restArtifacts()")))
          errors++;
      } else {
        if (!(assertT(equalMaps(m.restArtifacts(),
                                Map.merge(m1.restArtifacts(), m2)),
                      front + "restArtifacts()")))
          errors++;
      }
      // pick a name at random in m1 if not empty
      if (!(m1.isEmpty())) {
        Artifact a = randomArtifactFromMap(m1);
        int dx, dy;
        String n = a.name();
        int h = m.size().xPos();
        int w = m.size().yPos();
        do {
          dx = randomInt(1000) - 500;
          dy = randomInt(1000) - 500;
        } while (a.position().xPos() + dx < 0 || a.position().yPos() + dy < 0
                 || a.position().xPos() + dx >= h
                 || a.position().yPos() + dy >= w);
        if (!(assertT(equalMaps(m.moveArtifact(n, dx, dy),
                                Map.merge(m1.moveArtifact(n, dx, dy), m2)),
                      front + "moveArtifact(" + n + "," + dx + "," + dy + ")")))
          errors++;
      }
      if (!(m2.isEmpty())) {
        Artifact a = randomArtifactFromMap(m2);
        int dx, dy;
        String n = a.name();
        int h = m.size().xPos();
        int w = m.size().yPos();
        do {
          dx = randomInt(1000) - 500;
          dy = randomInt(1000) - 500;
        } while (a.position().xPos() + dx < 0 || a.position().yPos() + dy < 0
                 || a.position().xPos() + dx >= h
                 || a.position().yPos() + dy >= w);
        if (!(assertT(equalMaps(m.moveArtifact(n, dx, dy),
                                Map.merge(m1, m2.moveArtifact(n, dx, dy))),
                      front + "moveArtifact(" + n + "," + dx + "," + dy + ")")))
          errors++;
      }
      if (!(assertT(m.toString().equals(m1.toString() + " " + m2.toString()),
                    front + "toString()")))
        errors++;
      if (!(assertT(m.hashCode() == (m1.hashCode() + m2.hashCode()),
                    front + "hashCode()")))
        errors++;
      // again, need an equals tester here
      // I'll skip for now because that's the answer to Question 2
      return errors;
    } catch (Exception ex) {
      /*
       * If there was an exception anywhere in there, then we have a
       * problem
       */
      assertT(false, "Exception: " + ex.getMessage());
      return errors + 1;
    }
  }
  
  private static int testExceptions() {
    int errors = 0;
    // QUESTION 4: write this method to test that exceptions
    // prescribed in the specification are generated under
    // the right conditions
    // as usual, return the number of errors found
    /*
     * In empty(h,w) and artifact(h,w), both h and w must be greater
     * than 0. You are required to throw an IllegalArgumentException
     * with a suitable message if this is not the case. In
     * artifact(h,w,a), the position of artifact a must be within the
     * square defined by (0,0) and (h-1,w-1). You are required to
     * throw an IllegalArgumentException with a suitable message if
     * this is not the case. In merge(m1,m2), both m1 and m2 must have
     * the same size (both height and width). You are required to
     * throw an IllegalArgumentException with a suitable message if
     * this is not the case. In m.moveArtifact(n,dx,dy), if map m does
     * not contain an artifact named n, then you are required to throw
     * an Exception with a suitable message.
     */
    errors += testEmptyCreateExceptions(-1, -1);
    errors += testEmptyCreateExceptions(-1, 1);
    errors += testEmptyCreateExceptions(1, -1);
    errors += testEmptyFirstExceptions();
    errors += testEmptyMoveExceptions();
    errors += testEmptyRestExceptions();
    // valid coordinate, but outside of bounds of map
    Artifact a_invalid = Artifact.create(Coord.create(100, 100), "aoeu");
    Artifact a_valid = Artifact.create(Coord.create(1, 1), "aoeu");
    errors += testArtCreateExceptions(-1, -1, a_invalid);
    errors += testArtCreateExceptions(-1, 1, a_invalid);
    errors += testArtCreateExceptions(1, -1, a_invalid);
    errors += testArtCreateExceptions(10, 10, a_invalid);
    errors += testArtCreateExceptions(-1, -1, a_valid);
    errors += testArtCreateExceptions(-1, 1, a_valid);
    errors += testArtCreateExceptions(1, -1, a_valid);
    errors += testArtCreateExceptions(10, 10, a_valid);
    errors += testArtMoveExceptions();
    Map bigx = Map.artifact(1000, 10, a_valid);
    Map normal = Map.artifact(10, 10, a_valid);
    Map bigy = Map.artifact(10, 1000, a_valid);
    errors += testMergeCreateExceptions(bigx, normal);
    errors += testMergeCreateExceptions(bigy, normal);
    errors += testMergeCreateExceptions(bigx, bigy);
    errors += testMergeMoveExceptions(normal, normal);
    return errors;
  }
  
  // Tests exceptions for the creator of empty.
  private static int testEmptyCreateExceptions(int x, int y) {
    if (x >= 0 && y >= 0)
      return 0; // sanity check -- make sure that we're trying a wrong
                // input
    try {
      Map m = Map.empty(x, y);
      assertT(false, "Should throw exception in empty(" + x + "," + y + ")");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the firstArtifact of empty.
  private static int testEmptyFirstExceptions() {
    try {
      Map m = Map.empty(10, 10);
      m.firstArtifact();
      assertT(false, "Should throw exception in empty(10,10).firstArtifact()");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the restArtifact of empty.
  private static int testEmptyRestExceptions() {
    try {
      Map m = Map.empty(10, 10);
      m.restArtifacts();
      assertT(false, "Should throw exception in empty(10,10).restArtifacts()");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the moveArtifact of empty.
  private static int testEmptyMoveExceptions() {
    try {
      Map m = Map.empty(10, 10);
      m.moveArtifact("aoeu", 10, 10);
      assertT(false,
              "Should throw exception in empty(10,10).moveArtifact(\"aoeu\", 10, 10)");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the creator of Map.artifact.
  private static int testArtCreateExceptions(int x, int y, Artifact a) {
    if ((x >= 0 && y >= 0 && x >= a.position().xPos() && y >= a.position()
                                                               .yPos()))
      return 0; // sanity check -- make sure that we're trying a wrong
                // input
    try {
      Map m = Map.artifact(x, y, a);
      assertT(false, "Should throw exception in artifact(" + x + ", " + y
                     + ", " + a.toString() + ")");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the moveArtifact of Map.artifact.
  private static int testArtMoveExceptions() {
    try {
      Artifact a = Artifact.create(Coord.create(1, 1), "grok");
      Map m = Map.artifact(100, 100, a);
      m.moveArtifact("aoeu", 10, 10);
      assertT(false, "Should throw exception in artifact(100, 100, "
                     + a.toString() + ").moveArtifact(\"aoeu\", 10, 10)");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the creator of merge.
  private static int testMergeCreateExceptions(Map m1, Map m2) {
    try {
      Map.merge(m1, m2);
      assertT(false, "Should throw exception in merge(" + m1.toString() + ", "
                     + m2.toString() + ")");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  // Tests exceptions for the creator of merge.
  private static int testMergeMoveExceptions(Map m1, Map m2) {
    try {
      Map m = Map.merge(m1, m2);
      m.moveArtifact("thingee", 2, 33);
      assertT(false, "Should throw exception in merge(" + m1.toString() + ", "
                     + m2.toString() + ").moveArtifact(\"thingee\", 2, 33)");
      return 1;
    } catch (IllegalArgumentException ex) {
      return 0;
    }
  }
  
  /*
   * Result is expected to be true for passing tests, and false for
   * failing tests. If a test fails, we print out the provided message
   * so the user can see what might have gone wrong. Be sure to review
   * anything that doesn't make sense.
   */
  /* returns true if the assertion passed, and false otherwise! */
  private static boolean assertT(boolean result, String msg) {
    if (!result) {
      System.out.println("\n**ERROR**: " + msg);
    }
    System.out.print(".");
    return result;
  }
}
